//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift.org project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift.org project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import SwiftJavaConfigurationShared
import JExtractSwiftLib
import Testing

@Suite
struct JNIProtocolTests {
  var config: Configuration {
    var config = Configuration()
    config.enableJavaCallbacks = true
    return config
  }

  let source = """
    public protocol SomeProtocol {
      public func method() {}
      public func withObject(c: SomeClass) -> SomeClass {}
    }
  
    public protocol B {}
  
    public class SomeClass: SomeProtocol {
      public func makeClass() -> SomeClass {}
    }
  
    public func takeProtocol(x: some SomeProtocol, y: any SomeProtocol)
    public func takeGeneric<S: SomeProtocol>(s: S)
    public func takeComposite(x: any SomeProtocol & B)
  """

  @Test
  func generatesJavaInterface() throws {
    try assertOutput(
      input: source,
      config: config,
      .jni, .java,
      detectChunkByInitialLines: 1,
      expectedChunks: [
        """
        // Generated by jextract-swift
        // Swift module: SwiftModule
        
        package com.example.swift;
        
        import org.swift.swiftkit.core.*;
        import org.swift.swiftkit.core.util.*;
        """,
        """
        public interface SomeProtocol {
          ...
          public void method();
          ...
          public SomeClass withObject(SomeClass c, SwiftArena swiftArena$);
          ...
        }
        """
      ])
  }

  @Test
  func emitsDefault() throws {
    try assertOutput(
      input: source,
      config: config,
      .jni, .java,
      detectChunkByInitialLines: 1,
      expectedChunks: [
        """
        public interface SomeProtocol {
          ...
          public default SomeClass withObject(SomeClass c) {
            return withObject(c, SwiftMemoryManagement.GLOBAL_SWIFT_JAVA_ARENA);
          }
          ...
          public SomeClass withObject(SomeClass c, SwiftArena swiftArena$);
          ...
        }
        """
      ])
  }

  @Test
  func generatesJavaClassWithExtends() throws {
    try assertOutput(
      input: source,
      config: config,
      .jni, .java,
      detectChunkByInitialLines: 1,
      expectedChunks: [
        """
        public final class SomeClass implements JNISwiftInstance, SomeProtocol {
          ...
          public SomeClass makeClass(SwiftArena swiftArena$) {
          ...
        }
        """,
      ])
  }

  @Test
  func takeProtocol_java() throws {
    try assertOutput(
      input: source,
      config: config,
      .jni, .java,
      detectChunkByInitialLines: 1,
      expectedChunks: [
        """
        public static <_T0 extends SomeProtocol, _T1 extends SomeProtocol> void takeProtocol(_T0 x, _T1 y) {
          SwiftModule.$takeProtocol(x, y);
        }
        """,
        """
        private static native void $takeProtocol(java.lang.Object x, java.lang.Object y);
        """
      ])
  }

  @Test
  func takeProtocol_swift() throws {
    try assertOutput(
      input: source,
      config: config,
      .jni, .swift,
      detectChunkByInitialLines: 1,
      expectedChunks: [
        """
        final class _SwiftModule_takeGeneric_s_Wrapper: SwiftJavaSomeProtocolWrapper {
          let _javaSomeProtocolInterface: JavaSomeProtocol
          init(_javaSomeProtocolInterface: JavaSomeProtocol) {
            self._javaSomeProtocolInterface = _javaSomeProtocolInterface
          }
        }
        """,
        """
        @_cdecl("Java_com_example_swift_SwiftModule__00024takeProtocol__Ljava_lang_Object_2Ljava_lang_Object_2")
        func Java_com_example_swift_SwiftModule__00024takeProtocol__Ljava_lang_Object_2Ljava_lang_Object_2(environment: UnsafeMutablePointer<JNIEnv?>!, thisClass: jclass, x: jobject?, y: jobject?) {
          let xswiftObject$: (SomeProtocol)
          if environment.interface.IsInstanceOf(environment, x, _JNIMethodIDCache.JNISwiftInstance.class) != 0 {
            ...
            let xpointer$DynamicType$: Any.Type = unsafeBitCast(xpointer$TypeMetadataPointer$, to: Any.Type.self)
            guard let xpointer$RawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: xpointer$, in: environment))) else {
              fatalError("xpointer$ memory address was null")
            }
            #if hasFeature(ImplicitOpenExistentials)
            let xpointer$Existential$ = xpointer$RawPointer$.load(as: xpointer$DynamicType$) as! any (SomeProtocol)
            #else
            func xpointer$DoLoad<Ty>(_ ty: Ty.Type) -> any (SomeProtocol) {
              xpointer$RawPointer$.load(as: ty) as! any (SomeProtocol)
            }
            let xpointer$Existential$ = _openExistential(xpointer$DynamicType$, do: xpointer$DoLoad)
            #endif
            xswiftObject$ = xpointer$Existential$
          }
          else {
            xswiftObject$ = _SwiftModule_takeProtocol_x_Wrapper(_javaSomeProtocolInterface: JavaSomeProtocol(javaThis: x!, environment: environment))
          }
          let yswiftObject$: (SomeProtocol)
          if environment.interface.IsInstanceOf(environment, y, _JNIMethodIDCache.JNISwiftInstance.class) != 0 {
            ...
            yswiftObject$ = ypointer$Existential$
          }
          else {
            yswiftObject$ = _SwiftModule_takeProtocol_y_Wrapper(_javaSomeProtocolInterface: JavaSomeProtocol(javaThis: y!, environment: environment))
          }
          SwiftModule.takeProtocol(x: xswiftObject$, y: yswiftObject$)
        }
        """
      ]
    )
  }

  @Test
  func takeGeneric_java() throws {
    try assertOutput(
      input: source,
      config: config,
      .jni, .java,
      detectChunkByInitialLines: 1,
      expectedChunks: [
        """
        public static <S extends SomeProtocol> void takeGeneric(S s) {
          SwiftModule.$takeGeneric(s);
        }
        """,
        """
        private static native void $takeGeneric(java.lang.Object s);
        """
      ])
  }

  @Test
  func takeGeneric_swift() throws {
    try assertOutput(
      input: source,
      config: config,
      .jni, .swift,
      detectChunkByInitialLines: 1,
      expectedChunks: [
        """
        final class _SwiftModule_takeGeneric_s_Wrapper: SwiftJavaSomeProtocolWrapper {
          let _javaSomeProtocolInterface: JavaSomeProtocol
          init(_javaSomeProtocolInterface: JavaSomeProtocol) {
            self._javaSomeProtocolInterface = _javaSomeProtocolInterface
          }
        }
        """,
        """
        @_cdecl("Java_com_example_swift_SwiftModule__00024takeGeneric__Ljava_lang_Object_2")
        func Java_com_example_swift_SwiftModule__00024takeGeneric__Ljava_lang_Object_2(environment: UnsafeMutablePointer<JNIEnv?>!, thisClass: jclass, s: jobject?) {
          let sswiftObject$: (SomeProtocol)
          if environment.interface.IsInstanceOf(environment, s, _JNIMethodIDCache.JNISwiftInstance.class) != 0 {
            ...
            sswiftObject$ = spointer$Existential$
          }
          else {
            sswiftObject$ = _SwiftModule_takeGeneric_s_Wrapper(_javaSomeProtocolInterface: JavaSomeProtocol(javaThis: s!, environment: environment))
          }
          SwiftModule.takeGeneric(s: sswiftObject$)
        }
        """
      ]
    )
  }

  @Test
  func takeComposite_java() throws {
    try assertOutput(
      input: source,
      config: config,
      .jni, .java,
      detectChunkByInitialLines: 1,
      expectedChunks: [
        """
        public static <_T0 extends SomeProtocol & B> void takeComposite(_T0 x) {
          SwiftModule.$takeComposite(x);
        }
        """,
        """
        private static native void $takeComposite(java.lang.Object x);
        """
      ])
  }

  @Test
  func takeComposite_swift() throws {
    try assertOutput(
      input: source,
      config: config,
      .jni, .swift,
      detectChunkByInitialLines: 1,
      expectedChunks: [
        """
        final class _SwiftModule_takeComposite_x_Wrapper: SwiftJavaSomeProtocolWrapper, SwiftJavaBWrapper {
          let _javaSomeProtocolInterface: JavaSomeProtocol
          let _javaBInterface: JavaB
          init(_javaSomeProtocolInterface: JavaSomeProtocol, _javaBInterface: JavaB) {
            self._javaSomeProtocolInterface = _javaSomeProtocolInterface
            self._javaBInterface = _javaBInterface
          }
        }
        """,
        """
        @_cdecl("Java_com_example_swift_SwiftModule__00024takeComposite__Ljava_lang_Object_2")
        func Java_com_example_swift_SwiftModule__00024takeComposite__Ljava_lang_Object_2(environment: UnsafeMutablePointer<JNIEnv?>!, thisClass: jclass, x: jobject?) {
          let xswiftObject$: (SomeProtocol & B)
          if environment.interface.IsInstanceOf(environment, x, _JNIMethodIDCache.JNISwiftInstance.class) != 0 {
            let xpointer$ = environment.interface.CallLongMethodA(environment, x, _JNIMethodIDCache.JNISwiftInstance.memoryAddress, [])
            let xtypeMetadata$ = environment.interface.CallLongMethodA(environment, x, _JNIMethodIDCache.JNISwiftInstance.typeMetadataAddress, [])
            guard let xpointer$TypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: xtypeMetadata$, in: environment))) else {
              fatalError("xtypeMetadata$ memory address was null")
            }
            let xpointer$DynamicType$: Any.Type = unsafeBitCast(xpointer$TypeMetadataPointer$, to: Any.Type.self)
            guard let xpointer$RawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: xpointer$, in: environment))) else {
              fatalError("xpointer$ memory address was null")
            }
            #if hasFeature(ImplicitOpenExistentials)
            let xpointer$Existential$ = xpointer$RawPointer$.load(as: xpointer$DynamicType$) as! any (SomeProtocol & B)
            #else
            func xpointer$DoLoad<Ty>(_ ty: Ty.Type) -> any (SomeProtocol & B) {
              xpointer$RawPointer$.load(as: ty) as! any (SomeProtocol & B)
            }
            let xpointer$Existential$ = _openExistential(xpointer$DynamicType$, do: xpointer$DoLoad)
            #endif
            xswiftObject$ = xpointer$Existential$
          }
          else {
            xswiftObject$ = _SwiftModule_takeComposite_x_Wrapper(_javaSomeProtocolInterface: JavaSomeProtocol(javaThis: x!, environment: environment), _javaBInterface: JavaB(javaThis: x!, environment: environment))
          }
          SwiftModule.takeComposite(x: xswiftObject$)
        }
        """
      ]
    )
  }

  @Test
  func generatesProtocolWrappers() throws {
    try assertOutput(
      input: source,
      config: config,
      .jni, .swift,
      detectChunkByInitialLines: 1,
      expectedChunks: [
        """
        protocol SwiftJavaSomeProtocolWrapper: SomeProtocol {
          var _javaSomeProtocolInterface: JavaSomeProtocol { get }
        }
        """,
        """
        extension SwiftJavaSomeProtocolWrapper {
          public func method() {
            _javaSomeProtocolInterface.method()
          }        
          public func withObject(c: SomeClass) -> SomeClass {
            let cClass = try! JavaClass<JavaSomeClass>(environment: JavaVirtualMachine.shared().environment())
            let cPointer = UnsafeMutablePointer<SomeClass>.allocate(capacity: 1)
            cPointer.initialize(to: c)
            guard let unwrapped$ = _javaSomeProtocolInterface.withObject(cClass.wrapMemoryAddressUnsafe(Int64(Int(bitPattern: cPointer)))) else {
              fatalError("Upcall to withObject unexpectedly returned nil")
            }
            let result$MemoryAddress$ = unwrapped$.as(JavaJNISwiftInstance.self)!.memoryAddress()
            let result$Pointer = UnsafeMutablePointer<SomeClass>(bitPattern: Int(result$MemoryAddress$))!
            return result$Pointer.pointee
          }
        }
        """,
        """
        protocol SwiftJavaBWrapper: B {
          var _javaBInterface: JavaB { get }
        }
        """,
        """
        extension SwiftJavaBWrapper {
        }
        """
      ]
    )
  }
}
